package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"net"
	"strings"
	"time"
)

var (
	localPort  int
	remotePort int
)

func init() {
	// `localPort` 的命令行标志是 `-l`，如果没有提供 `-l` 参数，它的默认值是 5200
	flag.IntVar(&localPort, "l", 5200, "the user link port")
	// remotePort` 的命令行标志是 `-r`，如果没有提供 `-r` 参数，它的默认值是 3333
	// "the user link port" 和 "client listen port", 在命令行工具的帮助信息中显示
	flag.IntVar(&remotePort, "r", 3333, "client listen port")
}

type client struct {
	conn net.Conn
	// 数据传输通道
	read  chan []byte
	write chan []byte
	// 异常退出通道
	exit chan error
	// 重连通道
	reConn chan bool
}

// 从Client端读取数据
func (c *client) Read(ctx context.Context) {
	// 如果10秒钟内没有消息传输，则Read函数会返回一个timeout的错误
	_ = c.conn.SetReadDeadline(time.Now().Add(time.Second * 10))
	for {
		select {
		case <-ctx.Done():
			return
		default:
			data := make([]byte, 10240)
			n, err := c.conn.Read(data)
			if err != nil && err != io.EOF {
				if strings.Contains(err.Error(), "timeout") {
					// 设置读取时间为3秒，3秒后若读取不到, 则err会抛出timeout,然后发送心跳
					_ = c.conn.SetReadDeadline(time.Now().Add(time.Second * 3))
					// 将字节切片中的内容写入到连接中 将会发送一个ASCII值为105的"p"字符和ASCII值为102的"i"字符
					// 将字符串 "pi" 转换为一个字节切片 写入到网络连接中 用于发送数据到远程服务器或与其他设备通信
					c.conn.Write([]byte("pi"))
					continue
				}
				fmt.Println("从Client端读取数据出现错误...")
				c.exit <- err
				return
			}

			// 收到心跳包 则跳过
			if data[0] == 'p' && data[1] == 'i' {
				fmt.Println("server收到心跳包")
				continue
			}
			c.read <- data[:n]
		}
	}
}

// 将数据写入到Client端
func (c *client) Write(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			return
		case data := <-c.write:
			_, err := c.conn.Write(data)
			if err != nil && err != io.EOF {
				c.exit <- err
				return
			}
		}
	}
}

type user struct {
	conn net.Conn
	// 数据传输通道
	read  chan []byte
	write chan []byte
	// 异常退出通道
	exit chan error
}

// 从User端读取数据
func (u *user) Read(ctx context.Context) {
	// 如果200秒钟内没有消息传输，则Read函数会返回一个timeout的错误
	_ = u.conn.SetReadDeadline(time.Now().Add(time.Second * 200))
	for {
		select {
		case <-ctx.Done():
			return
		default:
			data := make([]byte, 10240)
			// u.conn连接中读取数据，并将读取的数据存入之前创建的data切片中，n是实际读取的字节数
			n, err := u.conn.Read(data)
			if err != nil && err != io.EOF {
				u.exit <- err
				return
			}
			u.read <- data[:n]
		}
	}
}

// 将数据写给User端
func (u *user) Write(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			return
		case data := <-u.write:
			_, err := u.conn.Write(data)
			if err != nil && err != io.EOF {
				u.exit <- err
				return
			}
		}
	}
}

func main() {
	// 解析命令行参数
	flag.Parse()

	// 是一个用于从 panic 恢复的函数。当一个函数触发 panic 时，程序的执行会立即停止，并且开始回溯。在回溯的过程中，每次遇到 defer 语句，
	// 都会执行其包含的函数。recover 函数可以捕获到这个 panic，并返回导致 panic 的值
	// 目的是在可能发生 panic 的地方设置一个“安全网”，这样即使发生了 panic，程序也不会完全崩溃，而是会打印出导致 panic 的错误，并继续执行后续的代码（如果有的话）
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println(err)
		}
	}()

	clientListener, err := net.Listen("tcp", fmt.Sprintf(":%d", remotePort))
	if err != nil {
		panic(err)
	}
	fmt.Printf("监听:%d端口, 等待client连接... \n", remotePort)

	userListener, err := net.Listen("tcp", fmt.Sprintf(":%d", localPort))
	if err != nil {
		panic(err)
	}
	fmt.Printf("监听:%d端口, 等待user连接.... \n", localPort)

	for {
		// 有Client来连接了
		clientConn, err := clientListener.Accept()
		if err != nil {
			panic(err)
		}

		fmt.Printf("有Client连接: %s \n", clientConn.RemoteAddr())

		client := &client{
			conn:   clientConn,
			read:   make(chan []byte),
			write:  make(chan []byte),
			exit:   make(chan error),
			reConn: make(chan bool),
		}

		userConnChan := make(chan net.Conn)
		go AcceptUserConn(userListener, userConnChan)

		go HandleClient(client, userConnChan)
	}
}

// 等待user连接
func AcceptUserConn(userListener net.Listener, connChan chan net.Conn) {
	userConn, err := userListener.Accept()
	if err != nil {
		panic(err)
	}
	fmt.Printf("user connect: %s \n", userConn.RemoteAddr())
	connChan <- userConn
}

func HandleClient(client *client, userConnChan chan net.Conn) {
	// context.Background()：返回一个空的上下文。这是一个特殊的上下文，不包含任何值或取消功能
	// context.WithCancel(ctx)：使用给定的上下文ctx创建一个新的上下文，该上下文有一个取消函数cancel
	// 调用 cancel() 会触发一个信号，使得所有正在等待或阻塞在这个上下文上的操作都应该停止
	ctx, cancel := context.WithCancel(context.Background())

	go client.Read(ctx)
	go client.Write(ctx)

	user := &user{
		read:  make(chan []byte),
		write: make(chan []byte),
		exit:  make(chan error),
	}

	defer func() {
		_ = client.conn.Close()
		_ = user.conn.Close()
		client.reConn <- true
	}()

	for {
		select {
		case userConn := <-userConnChan:
			user.conn = userConn
			go handle(ctx, client, user)
		case err := <-client.exit:
			fmt.Println("client出现错误, 关闭连接", err.Error())
			cancel()
			return
		case err := <-user.exit:
			fmt.Println("user出现错误，关闭连接", err.Error())
			cancel()
			return
		}
	}
}

// 将两个Socket通道链接
// 1. 将从user收到的信息发给client
// 2. 将从client收到信息发给user
func handle(ctx context.Context, client *client, user *user) {
	go user.Read(ctx)
	go user.Write(ctx)

	for {
		select {
		case userRecv := <-user.read:
			// 收到从user发来的信息
			client.write <- userRecv
		case clientRecv := <-client.read:
			// 收到从client发来的信息
			user.write <- clientRecv

		case <-ctx.Done():
			return
		}
	}
}
